package com.gitee.sergius.exceltool.input;

import com.gitee.sergius.exceltool.exception.ExcelException;
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
import org.apache.poi.ss.usermodel.Cell;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * <p>描述：从Excel导入到list<T>
 *
 * @author shawn yang
 * @version 1.00
 */
public class ExcelImporter {


    /**
     * 设置文件读取源
     * @param in
     * @return
     * @throws ExcelException
     */
    public ExcelImporter inputFile(InputStream in) throws ExcelException {
        if(in != null){
            try{
                POIFSFileSystem fs = new POIFSFileSystem(in);
                this.WORKBOOK = new HSSFWorkbook(fs);
            }catch(IOException e){
                throw new ExcelException("File input fail! ");
            }
        }
        return this;
    }

    /**
     * 设置导入起始行
     * @param row
     * @return
     */
    public ExcelImporter startRow(int row){
        if(row >= 0){
            this.START_ROW = row;
        }
        return this;
    }

    /**
     * 设置日期类型数据展示格式，不设置默认为"2010-11-12 12:10:10"
     * @param dataFormat 日期格式
     * @return 实例化对象
     */
    public ExcelImporter dataFormat(String dataFormat) {
        DATE_FORMAT = dataFormat;
        return this;
    }

    /**
     * 设置要转换成的封装实体类
     * @param c
     * @return
     */
    public ExcelImporter entityClass(Class c){
        if(c != null){
            this.CLAZZ = c;
        }
        return this;
    }

    /**
     * 列号到属性域的映射关系
     * @param reflectMap
     * @return
     */
    public ExcelImporter reflectMap(Map<String, Integer> reflectMap){
        if(reflectMap != null && !reflectMap.isEmpty()){
            this.REFLECT_MAP = reflectMap;
        }
        return this;
    }

    public ExcelImporter processMap(Map<String, FieldProcessor> processMap){
        if(processMap != null && !processMap.isEmpty()){
            this.PROCESS_MAP = processMap;
        }
        return this;
    }

    @SuppressWarnings("rawtypes")
    public <T> List<T> produce() throws ExcelException {
        List<T> tList = new ArrayList<>();
        HSSFSheet hssfSheet = this.WORKBOOK.getSheetAt(0);
        if (hssfSheet == null) {
            return null;
        }
        Class<T> c = this.CLAZZ;

        T t;

        for (int rowNum = this.START_ROW,len=hssfSheet.getLastRowNum(); rowNum <= len; rowNum++) {
            HSSFRow hssfRow = hssfSheet.getRow(rowNum);
            if (hssfRow != null) {
                try {
                    t = c.newInstance();
                } catch (Exception e) {
                    throw new ExcelException("Class " + c.getSimpleName() + " create instance fail !");
                }

                Set<String> fields = this.REFLECT_MAP.keySet();
                for (String field : fields) {
                    Field f ;
                    try {
                        f = c.getDeclaredField(field);
                    } catch (NoSuchFieldException e) {
                        throw new ExcelException("Field : " + field + " not found !");
                    }
                    String fieldTypeStr = f.getGenericType().toString().replace("class ", "");
                    String setMethodName = "set" + field.substring(0, 1).toUpperCase() + field.substring(1);
                    Integer columnIndex = this.REFLECT_MAP.get(field);
                    HSSFCell no = hssfRow.getCell(columnIndex);
                    String srcCalue = getValue(no) == null ? "" : getValue(no);

                    FieldProcessor processor = this.PROCESS_MAP.get(field);

                    if (processor != null) {
                        srcCalue = processor.process(srcCalue);
                    }

                    if (srcCalue != null && !"".equals(srcCalue)) {
                        int tailIndex = srcCalue.contains(".") ? srcCalue.indexOf('.') : srcCalue.length();

                        try {
                            if ("boolean".equals(fieldTypeStr)) {
                                Class paramType = boolean.class;
                                Method setMethod = c.getMethod(setMethodName, paramType);
                                boolean realValue = Boolean.parseBoolean(srcCalue);
                                setMethod.invoke(t, realValue);
                            } else if ("char".equals(fieldTypeStr)) {
                                Class paramType = char.class;
                                Method setMethod = c.getMethod(setMethodName, paramType);
                                char realValue = srcCalue.charAt(0);
                                setMethod.invoke(t, realValue);
                            } else if ("byte".equals(fieldTypeStr)) {
                                Class paramType = byte.class;
                                Method setMethod = c.getMethod(setMethodName, paramType);
                                byte realValue = Byte.parseByte(srcCalue.substring(0, tailIndex));
                                setMethod.invoke(t, realValue);
                            } else if ("short".equals(fieldTypeStr)) {
                                Class paramType = short.class;
                                Method setMethod = c.getMethod(setMethodName, paramType);
                                short realValue = Short.parseShort(srcCalue.substring(0, tailIndex));
                                setMethod.invoke(t, realValue);
                            } else if ("int".equals(fieldTypeStr)) {
                                Class paramType = int.class;
                                Method setMethod = c.getMethod(setMethodName, paramType);
                                int realValue = Integer.parseInt(srcCalue.substring(0, tailIndex));
                                setMethod.invoke(t, realValue);
                            } else if ("long".equals(fieldTypeStr)) {
                                Class paramType = long.class;
                                Method setMethod = c.getMethod(setMethodName, paramType);
                                long realValue = Long.parseLong(srcCalue.substring(0, tailIndex));
                                setMethod.invoke(t, realValue);
                            } else if ("float".equals(fieldTypeStr)) {
                                Class paramType = float.class;
                                Method setMethod = c.getMethod(setMethodName, paramType);
                                float realValue = Float.parseFloat(srcCalue);
                                setMethod.invoke(t, realValue);
                            } else if ("double".equals(fieldTypeStr)) {
                                Class paramType = double.class;
                                Method setMethod = c.getMethod(setMethodName, paramType);
                                double realValue = Double.parseDouble(srcCalue);
                                setMethod.invoke(t, realValue);
                            } else if ("java.lang.String".equals(fieldTypeStr)) {
                                Class paramType = Class.forName(fieldTypeStr);
                                Method setMethod = c.getMethod(setMethodName, paramType);
                                setMethod.invoke(t, srcCalue);
                            } else if ("java.lang.Integer".equals(fieldTypeStr)) {
                                Class paramType = Class.forName(fieldTypeStr);
                                Method setMethod = c.getMethod(setMethodName, paramType);
                                Integer realValue = Integer.valueOf(srcCalue.substring(0, tailIndex));
                                setMethod.invoke(t, realValue);
                            } else if ("java.lang.Boolean".equals(fieldTypeStr)) {
                                Class paramType = Class.forName(fieldTypeStr);
                                Method setMethod = c.getMethod(setMethodName, paramType);
                                Boolean realValue = Boolean.valueOf(srcCalue);
                                setMethod.invoke(t, realValue);
                            } else if ("java.lang.Byte".equals(fieldTypeStr)) {
                                Class paramType = Class.forName(fieldTypeStr);
                                Method setMethod = c.getMethod(setMethodName, paramType);
                                Byte realValue = Byte.valueOf(srcCalue.substring(0, tailIndex));
                                setMethod.invoke(t, realValue);
                            } else if ("java.lang.Long".equals(fieldTypeStr)) {
                                Class paramType = Class.forName(fieldTypeStr);
                                Method setMethod = c.getMethod(setMethodName, paramType);
                                Long realValue = Long.valueOf(srcCalue.substring(0, tailIndex));
                                setMethod.invoke(t, realValue);
                            } else if ("java.lang.Short".equals(fieldTypeStr)) {
                                Class paramType = Class.forName(fieldTypeStr);
                                Method setMethod = c.getMethod(setMethodName, paramType);
                                Short realValue = Short.valueOf(srcCalue.substring(0, tailIndex));
                                setMethod.invoke(t, realValue);
                            } else if ("java.lang.Double".equals(fieldTypeStr)) {
                                Class paramType = Class.forName(fieldTypeStr);
                                Method setMethod = c.getMethod(setMethodName, paramType);
                                Double realValue = Double.valueOf(srcCalue);
                                setMethod.invoke(t, realValue);
                            } else if ("java.lang.Float".equals(fieldTypeStr)) {
                                Class paramType = Class.forName(fieldTypeStr);
                                Method setMethod = c.getMethod(setMethodName, paramType);
                                Float realValue = Float.valueOf(srcCalue);
                                setMethod.invoke(t, realValue);
                            } else if ("java.lang.Character".equals(fieldTypeStr)) {
                                Class paramType = Class.forName(fieldTypeStr);
                                Method setMethod = c.getMethod(setMethodName, paramType);
                                Character realValue = srcCalue.charAt(0);
                                setMethod.invoke(t, realValue);
                            } else if ("java.math.BigDecimal".equals(fieldTypeStr)) {
                                Class paramType = Class.forName(fieldTypeStr);
                                Method setMethod = c.getMethod(setMethodName, paramType);
                                BigDecimal realValue = BigDecimal.valueOf(Double.parseDouble(srcCalue));
                                setMethod.invoke(t, realValue);
                            } else if ("java.util.Date".equals(fieldTypeStr)) {
                                Class paramType = Class.forName(fieldTypeStr);
                                Method setMethod = c.getMethod(setMethodName, paramType);

                                SimpleDateFormat sdf = new SimpleDateFormat(this.DATE_FORMAT);
                                Date realValue = null;
                                try {
                                    realValue = sdf.parse(srcCalue);
                                } catch (ParseException e) {
                                    e.printStackTrace();
                                }
                                setMethod.invoke(t, realValue);
                            }
                        } catch (Exception e) {
                            throw new ExcelException(e.getMessage());
                        }

                    }

                }

                tList.add(t);
            }

        }
        return tList;
    }


    private String getValue(HSSFCell hssfCell) {
        if (hssfCell.getCellType() == Cell.CELL_TYPE_BOOLEAN) {
            return String.valueOf(hssfCell.getBooleanCellValue());
        } else if (hssfCell.getCellType() == Cell.CELL_TYPE_NUMERIC) {
            DecimalFormat df = new DecimalFormat("0.000000");
            String formatStr = df.format(hssfCell.getNumericCellValue());
            return  replaceLastZero(formatStr);
        } else {
            return String.valueOf(hssfCell.getStringCellValue()).trim();
        }
    }

    private static String replaceLastZero(String str) {

        int i = str.length() - 1;
        if (str.charAt(i) != '0') {
            return str;
        } else if (str.indexOf('.') == -1) {
            return str;
        } else {
            while (str.charAt(i) == '0' || str.charAt(i) == '.') {
                if (str.charAt(i) == '.') {
                    i--;
                    break;
                } else {
                    i--;
                }
            }
            return str.substring(0, i + 1);
        }
    }


    private HSSFWorkbook WORKBOOK = new HSSFWorkbook();

    private String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";

    private Map<String,Integer> REFLECT_MAP ;

    private Class CLAZZ;

    private Map<String , FieldProcessor>  PROCESS_MAP = new HashMap<>();

    private int START_ROW = 1;




}
